package com.tianji.learning.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSearchDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.tianji.learning.enums.PlanStatus.PLAN_RUNNING;
import static com.tianji.learning.enums.PlanStatus.NO_PLAN;

/**
 * <p>
 * 学生课程表 服务实现类
 * </p>
 *
 * @author fx
 * @since 2024-08-20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;
    private final CatalogueClient catalogueClient;
    private final LearningRecordMapper recordMapper;

    /**
     * 01 根据userId批量添加课程 到课表中（day02 -- 随堂练习-01）
     *
     * @param userId
     * @param courseIds
     */
    @Override
    public void addLessons(Long userId, List<Long> courseIds) {
        //1 根据courseIds查询课程列表
        List<CourseSimpleInfoDTO> courseInfoList = courseClient.getSimpleInfoList(courseIds);

        //1.2 健壮性判断，判断课程信息是否为空
        if (ObjectUtil.isEmpty(courseInfoList)) {
            log.error("课程信息不存在，无法添加到课表");
            return;
        }

        //3 遍历课程信息集合，添加到课表中
        List<LearningLesson> lessons = new ArrayList<>(courseInfoList.size());

        for (CourseSimpleInfoDTO courseSimpleInfoDTO : courseInfoList) {

            //3.0 创建课程对象，添加新增时的基本属性 userId，课程id，创建时间
            LearningLesson lesson = new LearningLesson();
            lesson.setUserId(userId);
            lesson.setCourseId(courseSimpleInfoDTO.getId());
            LocalDateTime now = LocalDateTime.now();
            lesson.setCreateTime(now);

            //3.1 获取过期时间
            Integer validDuration = courseSimpleInfoDTO.getValidDuration();
            //3.2 课程有效期为空或小于0 ，课程信息有误，跳过
            if (ObjectUtil.isEmpty(validDuration) || validDuration <= 0) {
                log.error("课程{}-{}过期时间{}有误，无法添加到课表", courseSimpleInfoDTO.getId(), courseSimpleInfoDTO.getName(), validDuration);
                continue;
            }

            //3.3 课程有效期为9999 课程过期时间设置为null，表示永久又凶啊
            // 0<有效期<9999 ,添加过期时间
            if (validDuration == 9999) {
                lesson.setExpireTime(null);
            } else {
                lesson.setExpireTime(now.plusMonths(validDuration));
            }
            //3.4 添加课程到集合中
            lessons.add(lesson);
        }

        //4 批量添加课程
        this.saveBatch(lessons);
    }

    /**
     * 02 根据userId  分页查询我的课程列表（day02 -- 随堂练习-02）
     *
     * @param pageQuery
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> pageQueryMyLessons(PageQuery pageQuery) {
        //1 获取当前用户id userId
        Long userId = UserContext.getUser();

        //2 从learning-lesson 表中 分页查询获取课程数据   -- 根据最近学习时间排序
        //select * from learning_lesson where user_id = #{userId} order by latest_learn-time desc limit #{pageNo},#{pageSize}
        Page<LearningLesson> page = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .page(pageQuery.toMpPage("latest_learn_time", false));

        //2.1 如果返回结果中课程列表为空，直接返回空
        List<LearningLesson> records = page.getRecords();
        if (ObjectUtil.isEmpty(records)) {
            return PageDTO.empty(page);
        }

        //3 根据 records中 课程ids获取课程详细信息，并转换为Map，供后续高效使用
        Map<Long, CourseSimpleInfoDTO> courseInfoMap = queryCourseInfoAnd2Map(records);

        //
        //4 转换结果，将课程数据封装到 VO 中
        List<LearningLessonVO> vos = new ArrayList<>(records.size());
        for (LearningLesson lesson : records) {
            //4.1 转换格式 po —> vo
            LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
            //4.2 将课程信息中特定数据（名称，封面，总课时）添加到 vo 中
           /* for (CourseSimpleInfoDTO courseSimpleInfoDTO : courseInfoList) { //双重for循环，效率低
                if (record.getCourseId().equals(courseSimpleInfoDTO.getId())){
                    vo.setCourseName(courseSimpleInfoDTO.getName());
                    vo.setCourseCoverUrl(courseSimpleInfoDTO.getCoverUrl());
                    vo.setSections(courseSimpleInfoDTO.getSectionNum());
                }
            }*/

            CourseSimpleInfoDTO courseSimpleInfoDTO = courseInfoMap.get(lesson.getCourseId());
            vo.setCourseName(courseSimpleInfoDTO.getName());
            vo.setCourseCoverUrl(courseSimpleInfoDTO.getCoverUrl());
            vo.setSections(courseSimpleInfoDTO.getSectionNum());

            //4.3 存入vos集合中
            vos.add(vo);
        }

        //5 返回PageDTO
        return PageDTO.of(page, vos);
    }

    /**
     * 03 查询用户正在学习的课表 （day02 - 作业 - 01）
     *
     * @return
     */
    @Override
    public LearningLessonVO queryCurrentLesson() {
        //1 获取当前用户id userId
        Long userId = UserContext.getUser();

        //2 根据userId查询课表 按照最近学习时间排序 时间倒叙
        //3 第一个数据即为最新学习的课程
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
        // 健壮性判断，
        if (ObjectUtil.isEmpty(lesson)) {
            log.error("课程信息有误,无最新学习记录");
            return null;
        }


        //4 查询最新课程详细信息
        CourseFullInfoDTO courseInfoById = courseClient.getCourseInfoById(lesson.getCourseId(), true, true);
        if (ObjectUtil.isEmpty(courseInfoById)) {
            throw new BadRequestException("课程不存在");
        }
        //5 po转换为vo
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        //5.1 vo中添加添加课程名称，课程封面，总课时
        vo.setCourseName(courseInfoById.getName());
        vo.setCourseCoverUrl(courseInfoById.getCoverUrl());
        vo.setSections(courseInfoById.getSectionNum());

        //5.2 添加课表中总课程数量
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .count();
        vo.setCourseAmount(count);

        //5.3 添加最新学习的小节 名称及编号
        List<CataSimpleInfoDTO> cataSimpleInfoDTOS = catalogueClient.batchQueryCatalogue(Arrays.asList(lesson.getLatestSectionId()));
        if (ObjectUtil.isNotEmpty(cataSimpleInfoDTOS)) {
            CataSimpleInfoDTO cataSimpleInfoDTO = cataSimpleInfoDTOS.get(0);
            vo.setLatestSectionName(cataSimpleInfoDTO.getName());
            vo.setLatestSectionIndex(cataSimpleInfoDTO.getCIndex());
        }

        //6 返回
        return vo;
    }

    /**
     * 04 根据courseId查询课程的学习状态（day02 - 作业 - 02）
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonVO queryLessonById(Long courseId) {
        //1 获取当前用户id
        Long userId = UserContext.getUser();

        //2 根据userId和courseId查询课程
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        if (ObjectUtil.isEmpty(lesson)) {
            //log.error("课程信息有误");
            return null;
        }

        //3 po转vo 并返回
        return BeanUtils.copyBean(lesson, LearningLessonVO.class);
    }

    /**
     * 05 根据courseId删除课程（day02 - 作业 - 03）
     *
     * @param courseId
     * @return
     */
    @Override
    public void deleteCourseFromLesson(Long userId, Long courseId) {
        //1 如果userId为空，则获取当前userId
        if (userId == null) {
            userId = UserContext.getUser();
        }
        //2 根据userId和courseId删除课程
        this.lambdaUpdate()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .remove();
    }

    /**
     * 06 根据courseId查询学习人数（day02 - 作业 - 04）
     *
     * @param courseId
     * @return
     */
    @Override
    public Integer countLearningLessonByCourse(Long courseId) {
        //select count(*) from learning_lesson where course_id = ? and status in (0,1,2)
        Integer count = this.lambdaQuery()
                .eq(LearningLesson::getCourseId, courseId)
                .in(LearningLesson::getStatus,
                        LessonStatus.NOT_BEGIN.getValue(),
                        LessonStatus.LEARNING.getValue(),
                        LessonStatus.FINISHED.getValue())
                .count();
        return count;
    }

    /**
     * 07 根根据courseId 查询是否是当前用户课表中的有效课程（day02 - 作业 - 05）
     * 如果用户未登录，返回false
     * 如果用户登录 true 表示有效，false 表示无效
     *
     * @param courseId
     * @return
     */
    @Override
    public Long isLessonValid(Long courseId) {
        //1 获取当前用户id userId
        Long userId = UserContext.getUser();
        //1.1 当前用户不存在，返回false
        if (ObjectUtil.isEmpty(userId)) {
            return null;
        }
        //2 根据userId和courseId查询课程 ,要求是课程状态不为过期
        //select * from learning_lesson where user_id = ? and course_id = ? and status in (0,1,2)
        LearningLesson lesson = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .in(LearningLesson::getStatus,
                        LessonStatus.NOT_BEGIN.getValue(),
                        LessonStatus.LEARNING.getValue(),
                        LessonStatus.FINISHED.getValue())
                .one();
        //3 判断，如果为 0，返回false，否则返回true
        if (ObjectUtil.isNotEmpty(lesson)) {
            return lesson.getId();
        } else {
            return null;
        }
    }

    /**
     * 08 根据userId和courseId查询课表信息（day03-随堂-01）
     *
     * @param userId
     * @param courseId
     * @return
     */
    @Override
    public LearningLesson queryLessonByUserIdAndCourseId(Long userId, Long courseId) {
        return this.getOne(buildUserIdAndCourseIdWrapper(userId, courseId));
    }

    /**
     * 09 创建学习计划（day03-随堂-03）
     *
     * @param planDTO
     */
    @Override
    public void createPlans(LearningPlanDTO planDTO) {
        //1 查询课表信息
        LearningLesson lesson = this.getOne(buildUserIdAndCourseIdWrapper(UserContext.getUser(), planDTO.getCourseId()));
        AssertUtils.isNotNull(lesson, "课程信息不存在");

        //2 更新学习计划数据
        this.lambdaUpdate()
                .set(LearningLesson::getWeekFreq, planDTO.getFreq())
                .set(lesson.getPlanStatus() == NO_PLAN, LearningLesson::getPlanStatus, PLAN_RUNNING)
                .eq(LearningLesson::getId, lesson.getId())
                .update();
    }

    /**
     * 10 查询学习计划（day03-随堂-04）
     *
     * @param pageQuery
     * @return
     */
    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery pageQuery) {
        LearningPlanPageVO vo = new LearningPlanPageVO();
        //1 获取当前用户id
        Long userId = UserContext.getUser();
        //2 分页查询 我的课表 ,要求有学习计划 planStatus=PLAN_RUNNING
        // select * from learning_lesson where user_id = ? and plan_status =1 and status in (0,1) order by latest_learn_time desc limit ?,?
        Page<LearningLesson> pageResult = this.lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        List<LearningLesson> lessons = pageResult.getRecords();
        if (ObjectUtil.isEmpty(lessons)) {
            return vo.emptyPage(pageResult);
        }
        //3 根据courseId查询课程信息
        Map<Long, CourseSimpleInfoDTO> courseInfoDTOMap = queryCourseInfoAnd2Map(lessons);

        //4 统计学习记录中 本周已经学习的小节总数
        //select lessonId,count(*) as total from learning_record where user_id = ? and finished = 1
        // and finish_time between ? and ? group by lessonId
        //4.1 获取本周开始 及结束时间
        LocalDateTime weekBeginTime = DateUtils.getWeekBeginTime(LocalDate.now());
        LocalDateTime weekEndTime = DateUtils.getWeekEndTime(LocalDate.now());

        //4.2 发起统计 - 统计查询 每个课表 本周已学习的小节数
        QueryWrapper<LearningRecord> queryWrapper = new QueryWrapper<LearningRecord>();
        queryWrapper.select("lesson_id", "count(*) as total")
                .eq("user_id", userId)
                .eq("finished", 1)
                .between("finish_time", weekBeginTime, weekEndTime)
                .groupBy("lesson_id");
        List<Map<String, Object>> mapList = recordMapper.selectMaps(queryWrapper);
        Map<Long, Long> learnedSectionNumMap = mapList.stream().collect(Collectors.toMap(r -> (Long) r.get("lesson_id"), r -> (Long) r.get("total")));

        //5 循环填充缺失数据
        List<LearningPlanVO> voList = new ArrayList<>(lessons.size());
        for (LearningLesson lesson : lessons) {
            //5.1 po -> vo
            LearningPlanVO planVO = BeanUtils.copyBean(lesson, LearningPlanVO.class);

            //5.2 填充课程信息
            CourseSimpleInfoDTO courseInfo = courseInfoDTOMap.get(lesson.getCourseId());
            if (ObjectUtil.isNotEmpty(courseInfo)) {
                planVO.setCourseName(courseInfo.getName());
                planVO.setSections(courseInfo.getSectionNum());
            }

            //5.3 填充本周已学习小节数
            planVO.setWeekLearnedSections(learnedSectionNumMap.getOrDefault(lesson.getId(),0l).intValue());

            //5.4 添加vo到voList
            voList.add(planVO);
        }

        //6 本周计划学习的总小节数
        //select sum(week_freq) as total from learning_lesson where user_id = ? and plan_status =1 and status in (0,1)
        LearningLesson lesson = this.query()
                .select("sum(week_freq) as total_week_freq")
                .eq("user_id", userId)
                .eq("plan_status", PLAN_RUNNING)
                .in("status", LessonStatus.NOT_BEGIN, LessonStatus.LEARNING)
                .one();
        vo.setWeekTotalPlan(lesson.getTotalWeekfreq());

        //7 本周实际学习的总小节数
        long sum = learnedSectionNumMap.values().stream().mapToLong(l -> l.longValue()).sum();
        vo.setWeekFinished(Long.valueOf(sum).intValue());

        //8 封装成VO并返回
        vo.setWeekPoints(null);//TODO 本周学习积分
        return vo.pageInfo(pageResult.getTotal(), pageResult.getPages(), voList);
    }

    /**
     * 根据userId和courseId构建查询条件
     *
     * @param userId
     * @param courseId
     * @return
     */
    private LambdaQueryWrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {
        LambdaQueryWrapper<LearningLesson> queryWrapper = new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
        return queryWrapper;
    }

    /**
     * 根据records中的 的courseId 获取课程信息，并转换为Map，供后续高效使用
     *
     * @param records
     * @return
     */
    private Map<Long, CourseSimpleInfoDTO> queryCourseInfoAnd2Map(List<LearningLesson> records) {
        //3.1 获取课程ids
        /*List<Long> courseIds = new ArrayList<>(records.size());
        for (LearningLesson record : records) {
            Long id = record.getCourseId();
            ids.add(id);
        }*/
        Set<Long> courseIds = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());

        //3.2 根据ids查询课程信息
        List<CourseSimpleInfoDTO> courseInfoList = courseClient.getSimpleInfoList(courseIds);
        if (ObjectUtil.isEmpty(courseInfoList)) {
            throw new BadRequestException("课程信息不存在");
        }
        //3.3 将List转换为Map，后续高效使用
        Map<Long, CourseSimpleInfoDTO> courseInfoMap = courseInfoList.stream().collect(
                Collectors.toMap(CourseSimpleInfoDTO::getId, Function.identity()/*courseSimpleInfoDTO -> courseSimpleInfoDTO*/));
        return courseInfoMap;
    }
}
